package com.davfx.restream;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import com.davfx.httpnio.Http;

final class DatabaseFileAccessManager {
	private static final String OUT_DIRECTORY = "record";
	
	private static final char URL_ENCODING_CHAR = '%';
	private static final char SPECIAL_CHAR = '_';
	
	private static String normalize(String id) {
		String s = Http.Url.encode(id);
		s = s.replaceAll("\\" + SPECIAL_CHAR, String.valueOf(SPECIAL_CHAR) + SPECIAL_CHAR);
		return s.replaceAll("\\" + URL_ENCODING_CHAR, String.valueOf(SPECIAL_CHAR));
	}
	
	private final File directory = new File(OUT_DIRECTORY);
	private final Map<String, Pair> accesses = new HashMap<String, Pair>();
	
	public DatabaseFileAccessManager() {
	}
	
	public final class Access {
		private final String key;
		private Pair p = null;
		private long offset = 0;
		private final File f;
		private Access(File f) {
			this.f = f;
			key = f.getParentFile().getName() + Http.PATH_SEPARATOR + f.getName();
		}
		private void check() throws IOException {
			if (p != null) {
				return;
			}
			p = accesses.get(key);
			if (p == null) {
				f.getParentFile().mkdirs();
				p = new Pair(new RandomAccessFile(f, "rw"));
				accesses.put(key, p);
			}
			p.count++;
		}
		public long getOffset() {
			return offset;
		}
		public boolean eof() throws IOException {
			check();
			return (p.access.length() == offset);
		}
		public void writeLong(long i) throws IOException {
			check();
			p.access.seek(offset);
			p.access.writeLong(i);
			offset += 8;
		}
		public void writeInt(int i) throws IOException {
			check();
			p.access.seek(offset);
			p.access.writeInt(i);
			offset += 4;
		}
		public void writeString(String s) throws IOException {
			writeBuffer(ByteBuffer.wrap(s.getBytes(Http.UTF8_CHARSET)));
		}
		public void writeBuffer(ByteBuffer b) throws IOException {
			check();
			p.access.seek(offset);
			p.access.writeInt(b.remaining());
			offset += 4;
			p.access.write(b.array(), b.position(), b.remaining());
			offset += b.remaining();
		}
		public int readInt() throws IOException {
			check();
			p.access.seek(offset);
			int i = p.access.readInt();
			offset += 4;
			return i;
		}
		public long readLong() throws IOException {
			check();
			p.access.seek(offset);
			long i = p.access.readLong();
			offset += 8;
			return i;
		}
		public String readString() throws IOException {
			check();
			ByteBuffer b = readBuffer();
			return new String(b.array(), b.position(), b.remaining(), Http.UTF8_CHARSET);
		}
		public ByteBuffer readBuffer() throws IOException {
			check();
			p.access.seek(offset);
			int len = p.access.readInt();
			offset += 4;
			ByteBuffer b = ByteBuffer.allocate(len);
			while (b.hasRemaining()) {
				int l = p.access.read(b.array(), b.position(), b.remaining());
				if (l <= 0) {
					throw new IOException("End of file reached");
				}
				b.position(b.position() + l);
				offset += l;
			}
			b.flip();
			return b;
		}
		public void close() {
			if (p == null) {
				return;
			}
			p.count--;
			if (p.count == 0) {
				try {
					p.access.close();
				} catch (IOException e) {
				}
				accesses.remove(key);
			}
		}
	}
	
	public static final class Pair {
		private final RandomAccessFile access;
		private int count = 0;
		private Pair(RandomAccessFile access) {
			this.access = access;
		}
	}
	
	public File getDirectory(String id) {
		return new File(directory, normalize(id));
	}
	
	public Access find(File dir, long from) {
		File[] f = dir.listFiles();
		if (f == null) {
			return null;
		}
		long max = -1;
		File found = null;
		for (File file : f) {
			try {
				long l = Long.parseLong(file.getName());
				if (from >= l) {
					if ((found == null) || (l >= max)) {
						max = l;
						found = file;
					}
				}
			} catch (NumberFormatException e) {
			}
		}
		if (found == null) {
			return null;
		}
		return new Access(found);
	}
	
	public Access create(File dir, long from) {
		File f = new File(dir, String.valueOf(from));
		return new Access(f);
	}
	
}
